/* Archive Extractor
 * Copyright 2008-2009 Matthew Hoops
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $URL: http://archive-extractor.googlecode.com/svn/trunk/rar.cpp $
 * $Id: rar.cpp 2 2009-03-23 01:29:43Z clone2727 $
 *
 */

#include "rar.h"

RarArchive::RarArchive() : StandardArchive() {
}

std::vector<const char*> RarArchive::getArchiveExtensions() {
	std::vector<const char*> exList;
	exList.push_back("rar");
	return exList;
}

int RarArchive::detect(FILE* file) {
	static const byte magic[5] = { 'R', 'a', 'r', '!', 0x1a };
	for (byte i = 0; i < 5; i++)
		if (readByte(file) != magic[i])
			return 0;
	if (readUint16LE(file) != 7)
		return 0;
	return 100;
}

struct RarHeader {
	uint16 crc;
	byte type;
	uint16 flags;
	uint32 size;
	
	uint32 startPos;
}; 

bool RarArchive::extractFiles(FILE* file) {
	uint32 fileSize = getFileSize(file);
	
	while ((uint32)ftell(file) != fileSize) {
		RarHeader header;
		header.startPos = ftell(file);
		
		header.crc = readUint16LE(file);
		header.type = readByte(file);
		header.flags = readUint16LE(file);
		header.size = readUint16LE(file);
		
		if (header.flags & 0x8000 && header.type != 0x74)
			header.size += readUint32LE(file);
			
		switch (header.type) {
			case 0x72:
				// This is the marker block (Rar!). There's no other data here.
				break;
			case 0x74: {
				// This is a file/directory
				
				// Recall flags from the last header if requested
				if (header.flags & 0x10)
					header.flags = _lastHeaderFlags;
				
				uint32 compressedSize = readUint32LE(file);
				header.size += compressedSize; // The 0x8000 flag is always set, but we want the value separate here.
				
				uint32 uncompressedSize = readUint32LE(file);
				byte host_os = readByte(file);
				uint32 file_crc = readUint32LE(file);
				uint32 ftime = readUint32LE(file);
				byte rar_ver = readByte(file);
				byte packing_method = readByte(file);
				uint16 fileNameLength = readUint16LE(file);
				uint32 fileAttributes = readUint32LE(file);
				
				if (header.flags & 0x100)
					fseek(file, 8, SEEK_CUR); // Skip the high bytes (for a uint64) for compressed size and uncompressed size, respectively
				
				char* filename = new char[fileNameLength + 1];
				fread(filename, 1, fileNameLength, file);
				filename[fileNameLength] = 0;
				printf ("Extracting file \'%s\'...\n", filename);
				
				if (header.flags & 0x400)
					fseek(file, 8, SEEK_CUR); // Skip the Salt, whatever that is...
				
				if (header.flags & 0x1000)
					fseek(file, 5, SEEK_CUR); // Skip the Extra Time data
				
				// Calculate the actual version
				byte majorRarVersion = rar_ver / 10;
				byte minorRarVersion = rar_ver % 10;
				
				// Unsupported for now...
				if ((majorRarVersion == 2 && minorRarVersion == 9) || majorRarVersion > 2) {
					printf ("\tThis file requires RAR 2.9+ to extract. This is not supported yet.\n");
					fseek(file, compressedSize, SEEK_CUR);
					break;
				}
				
				// Unsupported for now...
				if (header.flags & 0x1) {
					printf ("\tThis file is continued from the last file. This is not supported yet.\n");
					fseek(file, compressedSize, SEEK_CUR);
					break;
				}
				
				// Unsupported for now...
				if (header.flags & 0x2) {
					printf ("\tThis file is continued in the next file. This is not supported yet.\n");
					fseek(file, compressedSize, SEEK_CUR);
					break;
				}
				
				// Unsupported for now...
				if (header.flags & 0x4) {
					printf ("\tThis file is password encrypted. This is not supported yet.\n");
					fseek(file, compressedSize, SEEK_CUR);
					break;
				}
				
				// Skip the comment segment
				if (header.flags & 0x8) {
					fseek(file, 5, SEEK_CUR);
					uint16 commentSize = readUint16LE(file) - 7; // Subtract the first seven bytes
					fseek(file, commentSize, SEEK_CUR);
				}
				
				
				
				printf ("This will be able to be extracted soon! Skipping for now, though.\n");
				fseek(file, compressedSize, SEEK_CUR);
				
				} break;
			case 0x73: // Main Header
			case 0x75: // Comment Header
			case 0x76: // AV Header
			case 0x77: // Sub Header
			case 0x78: // Protect Header
			case 0x79: // Sign Header
			case 0x7a: // New Sign Header
			case 0x7b: // End of Archive Header
				// Skip the useless data ;)
				fseek(file, header.size - (ftell(file) - header.startPos), SEEK_CUR);
				break;
			default:
				printf ("Unknown RAR header type %02x\n", header.type);
				return false;
		}
		
		_lastHeaderFlags = header.flags;
	}
	return false;
}
